﻿using System;
using System.Collections.Generic;
using Red.EngineData.Helpers;
using Red.EngineData.View;

namespace Red.EngineData.Protocol.Interop
{
    #region Enums

    /// <summary>
    /// Interop debug entry type
    /// </summary>
    public enum InteropDebugEntryType
    {
        Error,
        Warning,
        DebugMessage
    }

    #endregion

    #region Helper Classes

    /// <summary>
    /// Contains informations about interop call
    /// </summary>
    public sealed class InteropDebugEntry
    {
        public InteropDebugEntryType Type { get; set; }
        public string OwnerElement { get; set; }
        public string Description { get; set; }
        public string Expression { get; set; }
        public string File { get; set; }
        public int Line { get; set; }
        public int Order { get; set; }
    }

    /// <summary>
    /// Protocol statistics
    /// </summary>
    public class InteropStatistics
    {
        public ulong ActionsOutOfOrder { get; set; }
        public ulong ActionsHandled { get; set; }
        public ulong RemoteActionExceptions { get; set; }
        public int ActionsWaitingForResponse { get; set; }
        public string LastExceptions { get; set; }
        public double MinRequestTime { get; set; }
        public double MaxRequestTime { get; set; }

        public InteropStatistics()
        {
            MinRequestTime = double.MaxValue;
            MaxRequestTime = double.MinValue;
        }

        internal void UpdateTimers( double timeSpan )
        {
            if ( timeSpan < MinRequestTime )
                MinRequestTime = timeSpan;

            if ( timeSpan > MaxRequestTime )
                MaxRequestTime = timeSpan;
        }
    }

    /// <summary>
    /// Helper class which contains informations about correct interop call
    /// </summary>
    public class InteropDebugMessage
    {
        public string Message { get; set; }
        public InteropDebugInfo DebugInfo { get; set; }

        public InteropDebugMessage( string message, InteropDebugInfo debugInfo = null )
        {
            Message = message;
            DebugInfo = debugInfo;
        }
    }

    /// <summary>
    /// Helper class which contains informations about warning in interop call
    /// </summary>
    public class InteropWarning : InteropDebugMessage
    {
        public InteropWarning( string message, InteropDebugInfo debugInfo = null )
            : base( message, debugInfo )
        {
        }
    }

    #endregion

    /// <summary>
    /// General solution for debugging stuff in interop
    /// </summary>
    public sealed class InteropDebugger
    {
        #region Properties

        /// <summary>
        /// Interop protocol statistics
        /// </summary>
        public InteropStatistics Stats { get; private set; }

        /// <summary>
        /// Contains pending interop debug entries
        /// </summary>
        public List<InteropDebugEntry> PendingDebugEntries { get; private set; }

        #endregion

        #region Events

        public event Action<InteropDebugEntry> NewInteropDebugEntry;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor
        /// </summary>
        public InteropDebugger()
        {
            Stats = new InteropStatistics();
            PendingDebugEntries = new List<InteropDebugEntry>();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates interop debug entry based on protocol exception
        /// </summary>
        /// <param name="exception">Protocol exception</param>
        public void Log( ProtocolException exception )
        {
            var newEntry = new InteropDebugEntry
            {
                Type = InteropDebugEntryType.Error,
                Description = exception.Message,
                Expression = exception.DebugInfo.InteropExpression,
                File = exception.DebugInfo.CallerFilePath,
                Line = exception.DebugInfo.CallerLineNumber
            };

            AddDebugEntry( newEntry );
        }

        /// <summary>
        /// Creates interop debug entry based on interop warning
        /// </summary>
        /// <param name="warning">Interop warning</param>
        public void Log( InteropWarning warning )
        {
            var newEntry = new InteropDebugEntry
            {
                Type = InteropDebugEntryType.Warning,
                Description = warning.Message,
                Expression = ( warning.DebugInfo != null ) ? warning.DebugInfo.InteropExpression : "",
                File = ( warning.DebugInfo != null ) ? warning.DebugInfo.CallerFilePath : "",
                Line = ( warning.DebugInfo != null ) ? warning.DebugInfo.CallerLineNumber : -1
            };

            AddDebugEntry( newEntry );
        }

        /// <summary>
        /// Creates interop debug entry based on interop debug message
        /// </summary>
        /// <param name="debugMessage">interop debug message</param>
        public void Log( InteropDebugMessage debugMessage )
        {
            var newEntry = new InteropDebugEntry
            {
                Type = InteropDebugEntryType.DebugMessage,
                Description = debugMessage.Message,
                Expression = ( debugMessage.DebugInfo != null ) ? debugMessage.DebugInfo.InteropExpression : "",
                File = ( debugMessage.DebugInfo != null ) ? debugMessage.DebugInfo.CallerFilePath : "",
                Line = ( debugMessage.DebugInfo != null ) ? debugMessage.DebugInfo.CallerLineNumber : -1
            };

            AddDebugEntry( newEntry );
        }

        /// <summary>
        /// Flushes all pending interop debug entries
        /// </summary>
        public void FlushPendingDebugEntries()
        {
            SendToListeners();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Adds entry to collection
        /// </summary>
        /// <param name="newEntry">New interop debug entry</param>
        private void AddDebugEntry( InteropDebugEntry newEntry )
        {
            lock ( PendingDebugEntries )
            {
                PendingDebugEntries.Add( newEntry );
            }

            SendToListeners();
        }

        /// <summary>
        /// Removes all pending entries
        /// </summary>
        private void RemoveAllEntries()
        {
            lock ( PendingDebugEntries )
            {
                PendingDebugEntries.Clear();
            }
        }

        /// <summary>
        /// Sends pending entries to listeners
        /// </summary>
        private void SendToListeners()
        {
            if ( NewInteropDebugEntry == null )
                return;

            foreach ( var entry in PendingDebugEntries )
            {
                NewInteropDebugEntry( entry );
            }

            RemoveAllEntries();
        }

        #endregion
    }
}
